CMake 완전 정복

CMake 완전 정복

1. 왜 CMake를 사용해야 하는가?

1.1 CMake란 무엇인가: 단순한 빌드 도구가 아니다

CMake(Cross-Platform Make)는 현대 C/C++ 개발 환경에서 사실상의 표준으로 자리 잡은 도구다. 그러나 많은 개발자가 CMake를 단순히 make의 대체재나 복잡한 버전으로 오해하곤 한다. 이 오해를 바로잡는 것에서부터 CMake 학습은 시작된다. CMake는 빌드 도구(Build Tool)가 아니라, **빌드 시스템 생성기(Build System Generator)**다.1

이것이 의미하는 바는, CMake는 소스 코드를 직접 컴파일하고 링크하여 실행 파일을 만드는 것이 아니라는 점이다. 대신, 개발자가 작성한 CMakeLists.txt라는 설정 파일을 읽어들여, 각 운영체제와 개발 환경에 맞는 네이티브(native) 빌드 스크립트를 생성하는 역할을 한다.4 예를 들어, 리눅스나 macOS 환경에서는 전통적인 Makefile을 생성하고, 윈도우 환경에서는 Visual Studio 솔루션 파일(.sln)이나 NMake용 Makefile을, macOS에서는 Xcode 프로젝트를 생성할 수 있다.7

이러한 접근 방식은 엄청난 유연성을 제공한다. 개발자는 특정 플랫폼이나 IDE에 종속적인 빌드 스크립트를 일일이 관리할 필요 없이, 오직 CMakeLists.txt라는 단일 파일에 프로젝트의 구조와 빌드 방법을 추상적으로 기술하기만 하면 된다. CMake가 나머지 복잡한 과정, 즉 각 플랫폼에 맞는 빌드 환경을 구축하는 작업을 대신 처리해 주기 때문에, 진정한 의미의 크로스 플랫폼 개발이 가능해진다.1

1.2 Make와의 비교: CMake가 해결하는 근본적인 문제들

CMake의 가치를 제대로 이해하려면, 전통적인 make 시스템이 가진 한계를 먼저 살펴볼 필요가 있다. makeMakefile이라는 파일에 정의된 규칙에 따라 빌드를 자동화하는 강력한 도구지만, 프로젝트 규모가 커질수록 몇 가지 근본적인 문제에 부딪힌다.

첫째, 의존성 관리의 한계다. makeMakefile에 명시적으로 기술된 파일 간의 의존성만 인지한다.4 C/C++ 프로젝트에서는 헤더 파일(*.h, *.hpp)의 포함 관계가 복잡하게 얽히는 경우가 많은데, 새로운 헤더 파일이 추가되거나 기존 파일의 #include 구문이 변경될 때마다 개발자가 직접 Makefile을 수정해야 한다. 이 과정은 매우 번거롭고 실수를 유발하기 쉬우며, 대규모 프로젝트에서는 사실상 관리가 불가능에 가깝다.11

반면, CMake는 소스 코드를 분석하여 이러한 헤더 파일 의존성을 자동으로 파악한다.11 이를 통해 어떤 파일이 변경되었을 때, 그 파일에 의존하는 다른 파일들만 정확히 골라내어 다시 컴파일하는 **증분 빌드(Incremental Build)**를 매우 효과적으로 수행할 수 있다.11 이는 불필요한 재컴파일을 막아 대규모 프로젝트의 빌드 시간을 획기적으로 단축시킨다.

둘째, 플랫폼 종속성 문제다. Makefile은 셸 명령어와 문법에 기반하므로 본질적으로 유닉스 계열(리눅스, macOS) 환경에 강하게 결합되어 있다.9 윈도우 환경에서 이를 그대로 사용하는 것은 거의 불가능하며, 각 플랫폼마다 별도의 빌드 스크립트를 유지해야 하는 이중, 삼중의 노력이 필요하다. CMake는 프로젝트의 논리적 구조(“무엇을” 빌드할 것인가)와 실제 빌드 명령어(“어떻게” 빌드할 것인가)를 분리함으로써 이 문제를 해결한다. 개발자는 CMakeLists.txt에 프로젝트의 구조를 선언적으로 기술하기만 하면, CMake가 현재 시스템에 맞는 구체적인 빌드 방법을 담은 스크립트를 생성해주므로 진정한 크로스 플랫폼 개발을 실현할 수 있다.7

1.3 핵심 철학: 소스 외부 빌드(Out-of-Source Build)

CMake가 강력하게 권장하는 핵심 철학 중 하나는 **소스 외부 빌드(Out-of-Source Build)**다.13 이는 소스 코드가 위치한 디렉토리(Source Tree)와 CMake가 생성하는 빌드 관련 파일들(Makefile, 캐시 파일, 오브젝트 파일, 최종 실행 파일 등)이 위치할 디렉토리(Build Tree)를 물리적으로 분리하는 방식이다.13

예를 들어, 프로젝트 소스가 my_project/에 있다면, 그 안에 build/라는 디렉토리를 만들고 그 안에서 CMake를 실행하는 것이다. 이렇게 하면 다음과 같은 명확한 장점이 있다.

  • 소스 트리 오염 방지: 소스 코드 디렉토리가 빌드 과정에서 생성되는 온갖 임시 파일과 결과물로 지저분해지는 것을 막고, 순수하게 소스 코드만 관리할 수 있다.13
  • 간편한 클린(Clean): 빌드를 처음부터 다시 하고 싶거나 모든 결과물을 삭제하고 싶을 때, 단순히 빌드 디렉토리(build/) 전체를 삭제하기만 하면 된다. 소스 파일에는 아무런 영향을 주지 않는다.14
  • 다중 빌드 구성 지원: 하나의 소스 트리로 여러 종류의 빌드를 동시에 관리할 수 있다. 예를 들어, build_debug/, build_release/, build_for_arm/ 등 목적에 따라 여러 빌드 디렉토리를 만들고 각각 다른 설정으로 빌드하는 것이 매우 용이하다.13

이 튜토리얼의 모든 예제는 이러한 소스 외부 빌드 방식을 기본으로 하여 진행할 것이다. 이는 현대적인 CMake 워크플로우의 가장 기본적이고 중요한 습관이다.

2. 첫걸음: “Hello, World!” 프로젝트 빌드하기

2.1 설치 및 기본 환경 설정

CMake를 시작하기 위해 먼저 시스템에 설치해야 한다. 설치 방법은 운영체제에 따라 다르다.

  • 리눅스 (Debian/Ubuntu 계열):
sudo apt update
sudo apt install cmake
  • 리눅스 (Red Hat/Fedora 계열):
sudo dnf install cmake
  • macOS (Homebrew 사용 시):
brew install cmake
  • 윈도우: 공식 홈페이지 cmake.org/download/ 에서 윈도우용 인스톨러를 다운로드하여 설치한다.17 설치 과정에서 “Add CMake to the system PATH” 옵션을 선택하면 커맨드 프롬프트나 파워셸에서 바로 cmake 명령어를 사용할 수 있다.

설치가 완료되면, 터미널(또는 커맨드 프롬프트)에서 다음 명령어를 실행하여 버전 정보를 확인한다. 버전 정보가 정상적으로 출력되면 설치가 성공한 것이다.9

cmake --version

CMake는 cmake-gui라는 그래픽 사용자 인터페이스(GUI)도 제공하지만 13, 이 튜토리얼에서는 플랫폼에 구애받지 않고 자동화 스크립트 작성에도 용이한 커맨드라인 인터페이스(CLI)를 중심으로 설명한다.

2.2 최소 프로젝트 구조와 CMakeLists.txt

가장 간단한 CMake 프로젝트를 만들어보자. 먼저, 프로젝트를 위한 디렉토리를 생성하고 그 안에 main.cpp 소스 파일과 CMakeLists.txt 설정 파일을 만든다.17

hello_cmake/
├── CMakeLists.txt
└── main.cpp

main.cpp 파일에는 간단한 “Hello, World!” 출력 코드를 작성한다.17

// main.cpp
#include <iostream>

int main() {
std::cout << "Hello, CMake!" << std::endl;
return 0;
}

다음으로, 이 프로젝트의 빌드 방법을 CMake에 알려주기 위한 CMakeLists.txt 파일을 작성한다. 가장 기본적인 프로젝트는 단 세 줄의 명령어로 구성된다.1

# CMakeLists.txt

# 1. 이 스크립트가 요구하는 최소 CMake 버전을 명시한다.
#    이 버전보다 낮은 CMake에서는 에러가 발생한다.
cmake_minimum_required(VERSION 3.10)

# 2. 프로젝트의 이름, 버전, 사용할 프로그래밍 언어를 설정한다.
#    CXX는 C++를 의미한다.
project(HelloWorld VERSION 1.0 LANGUAGES CXX)

# 3. 소스 파일(main.cpp)을 사용하여 실행 파일(HelloWorld) 타겟을 생성하도록 지시한다.
add_executable(HelloWorld main.cpp)

각 명령어의 역할은 다음과 같다 1:

  • cmake_minimum_required(): 프로젝트가 사용하는 CMake 문법이 특정 버전 이상을 요구함을 명시한다. 호환성을 위해 반드시 최상단에 작성해야 한다.
  • project(): 프로젝트의 이름, 버전, 언어 등을 정의한다. 여기서 정한 이름은 IDE에서 프로젝트 이름으로 사용되거나, CMAKE_PROJECT_NAME과 같은 내장 변수에 저장된다.
  • add_executable(): 실행 파일(Executable) 타겟을 정의한다. 첫 번째 인자는 생성될 실행 파일의 이름이고, 그 뒤로는 빌드에 필요한 소스 파일들을 나열한다.

2.3 빌드 과정 따라하기: 생성(Generate)과 빌드(Build)

이제 작성한 CMakeLists.txt를 이용해 프로젝트를 빌드해보자. 앞서 설명한 소스 외부 빌드 원칙에 따라 진행한다.13

  1. 빌드 디렉토리 생성 및 이동: hello_cmake 디렉토리 안에서 build라는 이름의 디렉토리를 만들고 그 안으로 이동한다.
mkdir build
cd build
  1. 생성(Generate) 단계: build 디렉토리 안에서 다음 명령어를 실행한다.
cmake..

이 명령어는 CMake에게 상위 디렉토리(..)에 있는 CMakeLists.txt 파일을 읽어서, 현재 디렉토리(build/)에 현재 시스템에 맞는 네이티브 빌드 파일(예: 리눅스에서는 Makefile)을 생성하라고 지시하는 것이다.4 이 과정을 설정(Configuration) 또는 생성(Generation) 단계라고 부른다.

이 단계가 성공적으로 끝나면 build 디렉토리 안에 Makefile, CMakeCache.txt, CMakeFiles/ 등 여러 파일과 디렉토리가 생성된 것을 볼 수 있다. 이 파일들은 CMake가 빌드를 위해 내부적으로 사용하는 것들이므로, 버전 관리 시스템을 사용한다면 .gitignore 파일에 추가하여 추적되지 않도록 하는 것이 좋다.11

  1. 빌드(Build) 단계: 이제 생성된 빌드 스크립트를 실행하여 실제 컴파일과 링크를 수행한다.
cmake --build.

cmake --build. 명령어는 현재 디렉토리의 네이티브 빌드 시스템(리눅스에서는 make, 윈도우 Visual Studio 환경에서는 MSBuild 등)을 자동으로 찾아 실행해주는 편리하고 범용적인 명령어다.17 만약 리눅스나 macOS에서 Makefile이 생성되었다면, 전통적인 make 명령어를 직접 사용해도 결과는 동일하다.4

# 위 명령어와 동일하게 동작한다 (Makefile이 생성된 경우)
make

빌드 속도를 높이고 싶다면, CPU 코어를 여러 개 활용하는 병렬 빌드 옵션을 사용할 수 있다.3

# 4개의 코어를 사용하여 병렬 빌드
make -j4
  1. 실행: 빌드가 성공적으로 완료되면 build 디렉토리 안에 HelloWorld라는 실행 파일이 생성된다. 실행시켜 결과를 확인하자.
./HelloWorld

윈도우의 경우, 빌드 유형에 따라 build\Debug\HelloWorld.exe 와 같은 경로에 실행 파일이 생성될 수 있다.17

생성빌드의 두 단계를 구분하는 것이 CMake 워크플로우의 핵심이다. CMakeLists.txt 파일의 내용을 수정한 후에는, 변경된 설정을 빌드 시스템에 반영하기 위해 반드시 **생성 단계(cmake..)**를 다시 실행해야 한다. 생성 단계를 건너뛰고 빌드 단계(cmake --build.)만 반복해서는 변경사항이 적용되지 않는다.

2.4 빌드 유형(Build Type) 이해하기: DebugRelease

CMake는 단일 설정 생성기(single-configuration generator, 예: Makefile, Ninja)에서 CMAKE_BUILD_TYPE이라는 변수를 통해 빌드 유형을 제어한다. 주요 빌드 유형은 다음과 같다.3

  • Debug: 디버깅 정보(예: GCC의 -g 플래그)를 포함하고, 코드 최적화는 비활성화한다. GDB와 같은 디버거로 코드를 한 줄씩 실행하고 변수 값을 확인하는 데 필수적이다.3
  • Release: 모든 디버깅 정보를 제거하고, 컴파일러의 최대 최적화(예: GCC의 -O3 플래그)를 활성화한다. 최종 사용자에게 배포하기 위한 버전으로, 실행 속도가 가장 빠르다.3
  • RelWithDebInfo: Release 모드처럼 최적화는 활성화하지만, 디버깅 정보도 일부 포함한다. 최적화된 코드에서 발생하는 버그를 분석할 때 유용하다.
  • MinSizeRel: 실행 파일의 크기를 최소화하는 데 중점을 두고 최적화한다.

빌드 유형은 생성(Generate) 단계에서 -D 옵션을 사용하여 지정한다.16

# build 디렉토리로 이동 후
cd build

# Release 모드로 빌드 파일 생성
cmake -D CMAKE_BUILD_TYPE=Release..

# 이후 빌드
cmake --build.

만약 빌드 유형을 명시적으로 지정하지 않으면, 기본값은 비어 있거나 시스템에 따라 다를 수 있다. 따라서 개발 중에는 Debug, 배포 시에는 Release로 명확하게 설정해주는 것이 바람직하다.27

3. CMake 스크립트 언어 마스터하기

CMakeLists.txt 파일은 CMake 고유의 스크립트 언어로 작성된다. 이 언어의 핵심 요소인 변수, 흐름 제어, 내장 기능들을 이해하면 복잡한 프로젝트도 자유자재로 구성할 수 있다.

3.1 변수(Variables)의 모든 것

CMake의 모든 데이터는 변수를 통해 다뤄진다. 그 특징을 정확히 이해하는 것이 중요하다.

  • 기본 문법: 변수는 set() 명령어로 정의하고, ${변수명} 형태로 참조(사용)한다.1 CMake의 가장 독특한 특징 중 하나는

모든 변수를 내부적으로 문자열로 취급한다는 점이다.1

set(MY_VAR "Hello")
message("변수 값: ${MY_VAR}") # "변수 값: Hello" 출력
  • 리스트(List)로서의 문자열: CMake에서 리스트는 별도의 자료구조가 아니라, 세미콜론(;)으로 구분된 문자열이다. set() 명령어에 여러 값을 공백으로 구분하여 전달하면, CMake는 이들을 세미콜론으로 묶어 하나의 문자열로 저장한다.1
set(MY_SOURCES main.cpp utils.cpp)
# 내부적으로 MY_SOURCES 변수는 "main.cpp;utils.cpp" 문자열이 된다.
message(${MY_SOURCES}) # main.cpp;utils.cpp 출력

add_executable(my_app ${MY_SOURCES})
# 위 명령어는 add_executable(my_app main.cpp utils.cpp) 와 동일하게 해석된다.
  • 유효 범위(Scope): 변수는 기본적으로 현재 CMakeLists.txt 파일과 그 파일에서 add_subdirectory()로 포함하는 모든 하위 디렉토리에서 유효하다.1 즉, 부모 디렉토리에서 설정한 변수는 자식 디렉토리에서 읽을 수 있지만, 자식에서 설정한 변수는 부모에 영향을 주지 않는다.

  • PARENT_SCOPE: set(MY_VAR "value" PARENT_SCOPE)와 같이 사용하면, 변수를 현재 스코프가 아닌 부모 스코프에 설정할 수 있다.1

  • 캐시(Cache) 변수: 일반 변수는 CMake 실행이 끝나면 사라지지만, 캐시 변수는 빌드 디렉토리의 CMakeCache.txt 파일에 저장되어 여러 번의 CMake 실행에 걸쳐 그 값이 유지된다.30 주로 사용자가 빌드 옵션을 제어할 목적으로 사용된다.

  • set(MY_OPTION ON CACHE BOOL "Enable my optional feature"): MY_OPTION이라는 이름의 불리언(BOOL) 타입 캐시 변수를 만들고, 설명(docstring)을 추가한다.

  • option(MY_OPTION "Enable my optional feature" ON): 위 명령어는 BOOL 타입 캐시 변수를 만드는 더 간결한 표현이다.30

  • 캐시 변수는 한 번 설정되면 set() 명령어로 다시 덮어쓸 수 없다. 강제로 덮어쓰려면 FORCE 키워드를 사용해야 한다: set(MY_OPTION OFF CACHE BOOL "..." FORCE).1 이는 개발자가 설정한 기본값을 사용자가

-D 옵션 등으로 변경할 수 있게 하기 위함이다.

  • 환경(Environment) 변수: 시스템의 환경 변수는 $ENV{변수명} 형태로 읽을 수 있고, set(ENV{변수명} 값)으로 설정할 수 있다. 단, 여기서 설정한 값은 현재 실행 중인 CMake 프로세스에만 영향을 미치며, CMake 실행이 끝나면 사라진다.30

3.2 흐름 제어(Flow Control)

프로그래밍 언어처럼 CMake도 조건문과 반복문을 통해 빌드 로직을 제어할 수 있다.33

  • if/elseif/else/endif 조건문:

조건에 따라 다른 명령어를 실행할 때 사용한다. 문법은 if(조건)… endif() 형태다.33 CMake의

if문에서 가장 중요하고 혼동하기 쉬운 부분은 참/거짓 판별 기준이다. 이는 다른 프로그래밍 언어와 매우 다르므로 반드시 숙지해야 한다.33

값/표현식 (Value/Expression)평가 결과 (Result)설명 (Explanation)
ON, YES, TRUE, 1, 3.14참 (True)대소문자를 구분하지 않는 참 상수 및 0이 아닌 숫자.
OFF, NO, FALSE, 0거짓 (False)대소문자를 구분하지 않는 거짓 상수 및 숫자 0.
"" (빈 문자열), NOTFOUND거짓 (False)빈 문자열 또는 NOTFOUND 값은 거짓으로 취급된다.
VAR-NOTFOUND거짓 (False)이름이 -NOTFOUND로 끝나는 문자열은 거짓이다.
${defined_var} (값이 참 상수)참 (True)변수가 정의되어 있고 그 값이 참 상수 중 하나일 경우.
${defined_var} (값이 거짓 상수)거짓 (False)변수가 정의되어 있고 그 값이 거짓 상수 중 하나일 경우.
${no_such_var}거짓 (False)정의되지 않은 변수는 빈 문자열로 치환되므로 거짓이다.
"some string" (따옴표 묶인 문자열)거짓 (False)CMake 3.1 이상에서는 변수가 아닌 일반 문자열은 항상 거짓이다.

이러한 규칙을 이용해 플랫폼별로 다른 코드를 적용하는 것이 가능하다.

if(WIN32)
# 윈도우에서만 실행될 코드
message(STATUS "Building on Windows")
elseif(APPLE)
# macOS에서만 실행될 코드
message(STATUS "Building on macOS")
elseif(UNIX AND NOT APPLE)
# 리눅스에서만 실행될 코드
message(STATUS "Building on Linux")
endif()
  • foreach/endforeach 반복문:

리스트의 각 항목에 대해 동일한 작업을 반복할 때 사용한다.1

  • 리스트 순회:
set(MY_LIST a b c)
foreach(ITEM ${MY_LIST})
message("Current item is: ${ITEM}")
endforeach()
  • 범위 기반 순회:
# 0부터 5까지 순회 (5 포함)
foreach(I RANGE 5)
message("Number: ${I}")
endforeach()

# 10부터 20까지 2씩 증가하며 순회
foreach(J RANGE 10 20 2)
message("Even Number: ${J}")
endforeach()

3.3 유용한 내장 변수와 디버깅

CMake는 빌드 과정을 제어하고 정보를 얻는 데 유용한 여러 내장 변수를 제공한다.

  • 주요 내장 변수:

  • CMAKE_SOURCE_DIR: 최상위 CMakeLists.txt가 위치한 소스 디렉토리의 절대 경로.4

  • CMAKE_BINARY_DIR: 최상위 빌드 디렉토리의 절대 경로.

  • CMAKE_CURRENT_SOURCE_DIR: 현재 처리 중인 CMakeLists.txt 파일이 위치한 디렉토리의 절대 경로.24

  • CMAKE_CURRENT_BINARY_DIR: 현재 처리 중인 소스에 대응하는 빌드 디렉토리의 절대 경로.

  • PROJECT_NAME: project() 명령어로 설정한 프로젝트 이름.23

  • CMAKE_CXX_STANDARD: C++ 표준 버전을 지정하는 데 사용된다 (예: 11, 14, 17, 20).19

  • CMAKE_CXX_FLAGS: 모든 C++ 타겟에 적용될 컴파일러 플래그.

  • CMAKE_CXX_FLAGS_DEBUG, CMAKE_CXX_FLAGS_RELEASE: 각각 Debug, Release 빌드 유형에만 추가로 적용될 플래그.

  • 디버깅: 복잡한 CMakeLists.txt를 작성할 때 변수의 값이나 실행 흐름을 확인하는 것은 필수적이다. message() 명령어가 가장 기본적인 디버깅 도구다.

# 상태 메시지 출력 (앞에 -- 가 붙음)
message(STATUS "Checking variable MY_VAR: ${MY_VAR}")

# 경고 메시지 출력 (빌드는 계속됨)
message(WARNING "MY_VAR is not set to the expected value.")

# 치명적 오류 출력 (빌드 중단)
message(FATAL_ERROR "Required variable MY_VAR is missing.")

STATUS 모드는 일반적인 정보 출력에, WARNINGFATAL_ERROR는 각각 예상치 못한 상황과 빌드를 진행할 수 없는 심각한 오류 상황을 알리는 데 사용된다.23

4. 모던 CMake의 핵심: 타겟(Target) 기반 설계

CMake 3.0 이후, 빌드 시스템을 작성하는 방식에 큰 패러다임 변화가 생겼다. 이를 **모던 CMake(Modern CMake)**라 부르며, 그 핵심은 타겟(Target) 중심의 설계에 있다.

4.1 왜 모던 CMake인가? 구식 명령어의 한계

과거의 CMake(구식, Legacy)에서는 include_directories(), link_directories(), add_compile_options()와 같은 명령어를 주로 사용했다. 이 명령어들의 가장 큰 특징은 **디렉토리 범위(Directory Scope)**로 동작한다는 점이다.37 즉, 어떤

CMakeLists.txt 파일에서 이 명령어들이 호출되면, 그 효과는 해당 디렉토리와 add_subdirectory()로 추가된 모든 하위 디렉토리의 모든 타겟에 무차별적으로 적용된다.

이는 작은 프로젝트에서는 편리할 수 있지만, 프로젝트가 복잡해지면 심각한 문제들을 야기한다 37:

  • 의존성 누수(Dependency Leaking): 라이브러리 A를 빌드하기 위해 필요한 헤더 경로가, A를 사용하는 실행 파일 B뿐만 아니라 같은 디렉토리 스코프에 있는 전혀 상관없는 라이브러리 C에도 적용된다.
  • 전역 상태 문제: 모든 설정이 전역 변수처럼 동작하여, 어떤 설정이 어디에서 비롯되었는지 추적하기 어렵고, 설정 간의 충돌이 발생하기 쉽다.
  • 재사용성 및 유지보수성 저하: 특정 라이브러리를 다른 프로젝트에 가져다 쓰려고 할 때, 해당 라이브러리에 필요한 모든 설정을 다시 파악하고 적용해야 한다. 의존성이 라이브러리 자체에 캡슐화되어 있지 않기 때문이다.

4.2 모든 것은 타겟 중심: target_* 명령어 패러다임

모던 CMake는 이러한 문제들을 해결하기 위해 모든 속성(포함 디렉토리, 링크 라이브러리, 컴파일 옵션 등)을 개별 **타겟(Target)**에 직접 연결하는 방식을 제안한다.37 여기서 타겟이란

add_executable()이나 add_library() 명령어로 생성된 논리적인 빌드 단위를 말한다.

목적 (Purpose)구식 방식 (Legacy Way)모던 방식 (Modern Way)왜 모던 방식이 더 좋은가 (Why Modern is Better)
Include 경로 추가include_directories(path)target_include_directories(MyTarget... path)의존성을 타겟에 캡슐화하여 전역 오염을 방지한다.
컴파일 정의 추가add_definitions(-DMY_MACRO)target_compile_definitions(MyTarget... MY_MACRO)매크로 정의를 필요한 타겟에만 국한시킨다.
컴파일 옵션 추가add_compile_options(-Wall)target_compile_options(MyTarget... -Wall)컴파일 옵션을 타겟별로 다르게 설정할 수 있다.
라이브러리 링크link_directories(path) link_libraries(lib)target_link_libraries(MyTarget... lib)링크 의존성을 명시적으로 관리하고 전파 규칙을 제어한다.

이러한 target_* 계열 명령어들은 3 프로젝트의 구조를 훨씬 더 명확하고, 모듈화되고, 견고하게 만들어준다.

4.3 핵심 개념: PUBLIC, PRIVATE, INTERFACE

모던 CMake의 진정한 힘은 target_* 명령어와 함께 사용되는 PUBLIC, PRIVATE, INTERFACE 키워드에서 나온다. 이 키워드들은 타겟에 설정된 속성(사용 요건, Usage Requirements)이, 이 타겟을 사용하는 다른 타겟으로 어떻게 **전파(propagate)**될지를 정밀하게 제어한다. 이는 모던 CMake에서 가장 중요하고 강력한 개념이다.20

  • PRIVATE: “나만 쓸 것 (For me only)”
  • 여기에 명시된 속성은 오직 해당 타겟 자신을 빌드할 때만 사용된다.
  • 이 타겟을 링크하는 다른 타겟에게는 이 속성이 전혀 전파되지 않는다.
  • 사용 예시: 라이브러리의 구현 파일(.cpp) 내부에서만 #include하는 헤더 파일의 경로, 라이브러리 내부 구현에만 필요한 다른 라이브러리 링크.
  • PUBLIC: “나도 쓰고, 너도 써라 (For me and for you)”
  • 여기에 명시된 속성은 해당 타겟 자신을 빌드할 때 사용되고, 이 타겟을 링크하는 다른 타겟에게도 그대로 전파된다.
  • PRIVATEINTERFACE의 합집합과 같다.
  • 사용 예시: 라이브러리의 공개 헤더 파일(.h)에 직접적으로 노출되는 타입이나 함수가 포함된 다른 라이브러리의 헤더 경로 및 링크.
  • INTERFACE: “나는 안 쓰지만, 너는 써라 (Not for me, but for you)”
  • 여기에 명시된 속성은 해당 타겟 자신을 빌드하는 데는 사용되지 않고, 오직 이 타겟을 링크하는 다른 타겟에게만 전파된다.
  • 사용 예시: 헤더 파일만으로 구성된 라이브러리(Header-only library)가 다른 헤더 파일에 대한 의존성을 전달할 때.

이 키워드들은 단순한 옵션이 아니라, C++ 라이브러리의 API 경계를 빌드 스크립트 수준에서 명확하게 정의하고 강제하는 설계 도구다. PRIVATE은 구현 세부사항을 숨기고, PUBLICINTERFACE는 공개 API와 그 사용 계약을 정의한다. 이를 통해 개발자는 빌드 스크립트를 작성하는 과정에서 자연스럽게 자신의 코드 의존성 구조와 API 설계를 되돌아보게 되어, 결과적으로 더 높은 품질의 코드를 작성하게 된다.

4.4 target_link_libraries 실전 예제

PUBLIC, PRIVATE, INTERFACE의 동작을 구체적인 시나리오를 통해 이해해보자. 실행 파일 App이 라이브러리 Core에 의존하고, Core는 다시 외부 라이브러리 Log에 의존하는 구조를 가정한다.

# --- Logging Library (Log) ---
# 헤더 전용 라이브러리라고 가정
add_library(Log INTERFACE)
target_include_directories(Log INTERFACE
$<INSTALL_INTERFACE:include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
)

# --- Core Library (Core) ---
add_library(Core core.cpp core.h)
target_include_directories(Core PUBLIC
$<INSTALL_INTERFACE:include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
)

# Core의 구현부(core.cpp)에서만 Log 라이브러리를 사용한다고 가정
# 따라서 Log에 대한 의존성은 PRIVATE이다.
target_link_libraries(Core PRIVATE Log)

# --- Application (App) ---
add_executable(App main.cpp)

# App은 Core 라이브러리를 사용한다.
target_link_libraries(App PRIVATE Core)

위 시나리오를 분석해보자.

  1. CoreLogPRIVATE으로 링크했다. 이는 “로깅 기능은 Core의 내부 구현 사항일 뿐, Core를 사용하는 쪽에서는 알 필요가 없다“는 설계 의도를 나타낸다.
  2. AppCore를 링크할 때, CorePUBLIC 속성들(예: Coreinclude 디렉토리 경로)은 App에게 전파된다.
  3. 하지만 CorePRIVATE 속성인 Log에 대한 의존성은 App에게 전파되지 않는다. 따라서 App의 소스 코드(main.cpp)는 Log 라이브러리의 헤더를 직접 포함할 수 없으며, 컴파일러는 Log의 헤더 경로를 알지 못한다. 이것이 의존성 캡슐화다.

만약 Core의 공개 헤더 파일(core.h)에서 Log 라이브러리의 기능을 직접 사용해야 하는 상황으로 변경된다면 어떻게 해야 할까?

# core.h 에서 Log의 기능을 사용해야 할 경우, 의존성을 PUBLIC으로 변경해야 한다.
target_link_libraries(Core PUBLIC Log)

이렇게 변경하면, AppCore를 링크할 때 Log에 대한 의존성(include 경로 등)까지 함께 전파받게 된다. 따라서 AppCoreLog를 모두 사용하는 데 필요한 모든 정보를 얻게 되어 정상적으로 빌드될 수 있다.

5. 프로젝트 구조화 및 의존성 관리

프로젝트가 커지면 소스 코드를 기능별 모듈로 나누고 외부 라이브러리를 효율적으로 관리하는 능력이 중요해진다. CMake는 이를 위한 강력한 기능들을 제공한다.

5.1 라이브러리 생성: add_library

add_library() 명령어는 소스 파일들을 묶어 라이브러리 타겟을 생성한다.23 라이브러리에는 여러 종류가 있다.

  • STATIC (정적 라이브러리): .a(리눅스/macOS) 또는 .lib(윈도우) 확장자를 가진다. 정적 라이브러리는 컴파일 시점에 라이브러리의 코드가 실행 파일에 그대로 복사되어 포함된다. 배포가 간편하지만, 여러 실행 파일이 동일한 라이브러리를 사용할 경우 코드 중복으로 전체 용량이 커질 수 있다.46

  • SHARED (공유/동적 라이브러리): .so(리눅스), .dylib(macOS), .dll(윈도우) 확장자를 가진다. 공유 라이브러리는 실행 파일에 포함되지 않고, 프로그램이 실행될 때 운영체제에 의해 메모리에 로드되어 연결된다. 코드 재사용성이 높아 전체 용량을 줄일 수 있지만, 배포 시 해당 라이브러리 파일을 함께 제공해야 한다.46 공유 라이브러리를 빌드할 때는 보통 위치 독립적 코드(Position-Independent Code)로 컴파일해야 하므로, 관련 속성을 설정해야 할 수 있다.47

add_library(MyStaticLib STATIC my_static_lib.cpp)
add_library(MySharedLib SHARED my_shared_lib.cpp)
  • BUILD_SHARED_LIBS 변수: add_library()에서 타입을 명시하지 않으면 이 변수의 값에 따라 기본 타입이 결정된다. option(BUILD_SHARED_LIBS "Build shared libraries" ON)과 같이 프로젝트 상단에 옵션을 추가하면, 사용자가 -D BUILD_SHARED_LIBS=OFF 와 같은 명령으로 정적/동적 빌드를 쉽게 선택할 수 있다.47

  • OBJECT 라이브러리: 소스 파일을 컴파일하여 오브젝트 파일(.o)까지만 생성하고, 실제 라이브러리로 묶지는 않는다. 이 오브젝트 파일들은 다른 add_libraryadd_executable 타겟에서 $TARGET_OBJECTS:objlib 형태로 참조하여 재사용할 수 있다. 동일한 소스 코드로 정적 라이브러리와 동적 라이브러리를 모두 만들어야 할 때, 소스를 두 번 컴파일하는 낭비를 막아주는 매우 효율적인 방법이다.46

# 1. 소스 파일들을 한 번만 컴파일하여 오브젝트 라이브러리 생성
add_library(MyLibObjects OBJECT src1.cpp src2.cpp)
set_property(TARGET MyLibObjects PROPERTY POSITION_INDEPENDENT_CODE ON) # 공유 라이브러리를 위해 PIC 옵션 설정

# 2. 오브젝트 파일들을 이용하여 정적/동적 라이브러리 생성
add_library(MyLibStatic STATIC $<TARGET_OBJECTS:MyLibObjects>)
add_library(MyLibShared SHARED $<TARGET_OBJECTS:MyLibObjects>)

5.2 하위 디렉토리 관리: add_subdirectory

add_subdirectory(dir_name) 명령어는 프로젝트를 논리적인 모듈 단위로 구성하는 핵심적인 방법이다.4 이 명령어가 호출되면, CMake는 지정된 dir_name 디렉토리로 들어가 그 안에 있는 CMakeLists.txt 파일을 즉시 처리한다.

이는 프로젝트를 다음과 같이 계층적으로 구조화할 수 있게 해준다 6:

my_project/
├── CMakeLists.txt      # 최상위 CMakeLists.txt
├── src/
│   ├── CMakeLists.txt  # 실행 파일 타겟 정의
│   └──...
├── libs/
│   ├── mylib1/
│   │   ├── CMakeLists.txt # mylib1 타겟 정의
│   │   └──...
│   └── mylib2/
│       ├── CMakeLists.txt # mylib2 타겟 정의
│       └──...
└── tests/
├── CMakeLists.txt      # 테스트 타겟 정의
└──...

최상위 CMakeLists.txt에서는 다음과 같이 각 하위 모듈을 빌드에 포함시킨다.

# 최상위 CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(MyProject)

# 라이브러리 모듈 추가
add_subdirectory(libs/mylib1)
add_subdirectory(libs/mylib2)

# 주 소스 코드 모듈 추가
add_subdirectory(src)

# 테스트 모듈은 옵션으로 추가
option(BUILD_TESTING "Build the tests" ON)
if(BUILD_TESTING)
add_subdirectory(tests)
endif()

add_subdirectory는 새로운 변수 스코프를 생성하므로, 각 하위 디렉토리의 CMakeLists.txt는 독립적인 설정을 가질 수 있어 모듈 간의 간섭을 최소화한다.1

5.3 외부 라이브러리 연동: find_package

현대 C++ 개발은 외부 라이브러리 없이는 불가능하다. find_package()는 시스템에 설치된 외부 라이브러리(패키지)를 찾고, 빌드에 필요한 정보를 가져오는 표준적인 방법이다.4

find_package(<PackageName>)
  • <PackageName>: 찾으려는 패키지의 이름 (예: Boost, Qt5, OpenCV).
  • ``: 이 키워드가 있으면 패키지를 찾지 못했을 때 CMake가 오류를 발생시키며 중단된다.
  • ``: 패키지의 특정 컴포넌트(모듈)만 필요할 때 명시한다 (예: Boostfilesystem, program_options).

패키지를 성공적으로 찾으면, CMake는 두 가지 방식으로 정보를 제공한다.

  1. 구식 변수 방식: PackageName_FOUND, PackageName_INCLUDE_DIRS, PackageName_LIBRARIES 와 같은 변수들을 설정한다.
  2. 모던 Imported Target 방식: PackageName::ComponentName 형태의 Imported Target을 생성한다.

모던 CMake에서는 변수를 직접 사용하는 구식 방식보다 Imported Target을 사용하는 것이 강력하게 권장된다.6 Imported Target을 target_link_libraries()에 사용하면, 해당 라이브러리를 링크하는 것뿐만 아니라 필요한 헤더 경로, 컴파일 정의 등 모든 사용 요건(usage requirements)이 자동으로 전파되어 처리되기 때문이다.

실전 예제: Boost 라이브러리 연동 6

cmake_minimum_required(VERSION 3.15)
project(BoostUser CXX)

# Boost 라이브러리의 program_options와 filesystem 컴포넌트를 찾는다.
# 최소 1.70.0 버전을 요구하며, 찾지 못하면 빌드를 중단한다 (REQUIRED).
find_package(Boost 1.70.0 REQUIRED COMPONENTS program_options filesystem)

add_executable(my_app main.cpp)

# 모던 방식으로 Imported Target을 링크한다.
# 이렇게 하면 링크, 헤더 경로, 의존성(filesystem은 system에 의존) 등이 자동으로 처리된다.
target_link_libraries(my_app PRIVATE Boost::program_options Boost::filesystem)

이처럼 add_subdirectory로 내부 모듈을 구성하고, find_package로 외부 의존성을 가져온 뒤, target_link_libraries를 통해 이들을 모두 타겟이라는 일관된 개념으로 연결하는 것이 모던 CMake 프로젝트 구조화의 핵심이다. 내부 라이브러리든, 외부 라이브러리든 관계없이 동일한 target_* 명령어로 다룰 수 있다는 점이 CMake의 복잡성을 크게 낮춰주는 열쇠다.

6. 배포 및 고급 주제

6.1 설치(Installation) 규칙 정의: install 명령어

개발이 완료된 실행 파일이나 라이브러리를 시스템의 특정 위치(예: /usr/local/bin, /usr/local/lib)에 복사하여 다른 사용자가 사용하거나 다른 프로그램이 참조할 수 있도록 하는 과정을 ’설치(Installation)’라고 한다. install() 명령어는 make install 또는 cmake --build. --target install 명령이 실행될 때 어떤 파일을 어디로 복사할지를 정의한다.24

  • 타겟 설치: add_executable이나 add_library로 생성된 타겟(실행 파일, 라이브러리)을 설치한다.
# my_app 실행 파일은 bin 디렉토리에, MyLib 라이브러리는 lib 디렉토리에 설치
install(TARGETS my_app MyLib
RUNTIME DESTINATION bin   # 실행 파일 (RUNTIME)
LIBRARY DESTINATION lib   # 공유 라이브러리 (LIBRARY)
ARCHIVE DESTINATION lib   # 정적 라이브러리 (ARCHIVE)
)
  • 파일 및 디렉토리 설치: 헤더 파일, 설정 파일, 문서 등 임의의 파일을 설치한다.
# 공개 헤더 파일들을 include 디렉토리에 설치
install(DIRECTORY include/ DESTINATION include)

# 라이선스 파일을 share/my_app 디렉토리에 설치
install(FILES LICENSE DESTINATION share/my_app)
  • CMAKE_INSTALL_PREFIX: 설치가 이루어질 최상위 기본 경로를 지정하는 중요한 변수다. 기본값은 유닉스 계열에서는 보통 /usr/local이다.23 사용자는 CMake 실행 시 이 변수 값을 변경하여 원하는 위치에 프로그램을 설치할 수 있다.
# 기본 경로(/usr/local) 대신 홈 디렉토리 아래에 설치
cmake.. -D CMAKE_INSTALL_PREFIX=~/my_install_dir
make install

install() 명령어의 DESTINATION 경로는 이 CMAKE_INSTALL_PREFIX에 대한 상대 경로로 지정된다.

install() 명령어는 단순히 파일을 복사하는 것을 넘어, EXPORT 옵션과 함께 사용될 때 강력한 패키징 기능을 제공한다. install(EXPORT...)를 사용하면, 내 프로젝트가 생성한 타겟 정보를 담은 MyProjectConfig.cmake 파일을 생성하고 설치할 수 있다. 이렇게 패키징된 라이브러리는 다른 프로젝트에서 find_package(MyProject) 명령 한 줄로 쉽게 찾고 사용할 수 있게 된다. 이는 C++ 생태계에서 라이브러리를 공유하고 재사용하는 표준적인 방법이다.

6.2 함수(Functions)와 매크로(Macros)의 차이

CMakeLists.txt에서 반복적으로 사용되는 코드 묶음은 함수(function)나 매크로(macro)로 캡슐화하여 재사용성을 높일 수 있다. 둘은 비슷해 보이지만 결정적인 차이가 있다.1

  • 함수 (functionendfunction):
  • 독립적인 변수 스코프(Scope)를 가진다. 함수 내에서 set()으로 정의하거나 변경한 변수는 함수 바깥의 변수에 영향을 주지 않는다. 함수 호출이 끝나면 함수 내의 모든 변수는 사라진다.
  • 함수에 전달된 인자들은 ARGC (인자 개수), ARGV (모든 인자 리스트), ARGV0, ARGV1 (개별 인자) 등의 변수로 함수 내에서 접근할 수 있다.
  • 부작용(side effect)이 적어 예측 가능하고 안전한 코드를 작성하는 데 유리하다.
  • 매크로 (macroendmacro):
  • 별도의 스코프를 가지지 않는다. 매크로는 호출된 위치의 코드로 그대로 치환되는 것처럼 동작한다. 따라서 매크로 내에서 변수를 set()하면 호출자의 스코프에 있는 변수가 직접 변경된다.
  • 인자 역시 단순 텍스트 치환 방식으로 처리된다. 이로 인해 인자로 전달된 변수 이름이 의도치 않게 해석되는 등 예기치 않은 동작이 발생할 수 있다.

언제 무엇을 쓸까?

일반적으로 독립적인 작업을 수행하고 호출자 환경에 영향을 주지 않아야 하는 경우에는 함수를 사용하는 것이 훨씬 안전하고 권장된다. 간단한 코드 조각을 반복적으로 삽입하거나, 호출자의 변수를 의도적으로 수정해야 하는 제한적인 경우에만 매크로 사용을 고려할 수 있다. 대부분의 상황에서는 함수가 더 나은 선택이다.


이 튜토리얼을 통해 CMake의 기본 개념부터 모던 CMake의 핵심 철학, 그리고 실제 프로젝트에 적용하는 방법까지 살펴보았다. CMake는 처음에는 복잡해 보일 수 있지만, 그 구조와 원리를 이해하면 어떤 복잡한 C/C++ 프로젝트라도 플랫폼에 독립적으로, 체계적으로, 그리고 효율적으로 관리할 수 있는 강력한 무기가 될 것이다. 여기에 소개된 개념들을 바탕으로 실제 프로젝트에 꾸준히 적용하며 연습하는 것이 CMake 전문가로 가는 가장 빠른 길이다.

7. 참고 자료

  1. Cmake가 무엇인가?, https://medium.com/@yjo/cmake%EA%B0%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-9a0091adafb
  2. [CMake] CMake란 - domybestinlife - 티스토리, https://domybestinlife.tistory.com/340
  3. CMake Primer - velog, https://velog.io/@noogoolgga/CMake-Primer
  4. CMake를 사용해보자! - SCRIPTS BY - 티스토리, https://nx006.tistory.com/36
  5. ralpioxxcs.github.io, https://ralpioxxcs.github.io/post/cmake_1/#:~:text=cmake%EB%9E%80%20cross%20platform%20%EA%B8%B0%EB%B0%98,%EB%B9%8C%EB%93%9C%EB%A5%BC%20%ED%8E%B8%EB%A6%AC%ED%95%98%EA%B2%8C%20%ED%95%B4%EC%A4%80%EB%8B%A4.
  6. Building with CMake - Boost, https://www.boost.org/doc/user-guide/building-with-cmake.html
  7. CMake란 무엇인가?, https://gtrfx.github.io/2019/03/07/what-is-cmake.html
  8. cmake를 사용하는 이유는 무엇인가요?(make와의 비교) - Reddit, https://www.reddit.com/r/C_Programming/comments/heoakd/what_is_the_point_of_cmakevs_make/?tl=ko
  9. CMake 정리 Part1 - Overview :: My Devlog - 기록 & 공부용 개발 블로그, https://ralpioxxcs.github.io/post/cmake_1/
  10. #번외 CMake란 무엇인가? - 시크한 공돌이의 IT 블로그, https://chiccoder.tistory.com/46
  11. CMake 배우기(1) - 쉬운 생각, 꾸준한 노력 - 티스토리, https://easy-study-note.tistory.com/9
  12. [리눅스] cmake에 대한 개념 설명과 CMakeLists.txt 작성법 - REAKWON - 티스토리, https://reakwon.tistory.com/123
  13. Getting Started - Mastering CMake, https://cmake.org/cmake/help/book/mastering-cmake/chapter/Getting%20Started.html
  14. CMake and out-of-source build - IDisposable Thoughts, https://cprieto.com/posts/2016/10/cmake-out-of-source-build.html
  15. CMake 가 무엇일까? - Rocknz - 티스토리, https://rocknz.tistory.com/entry/CMake-%EA%B0%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C
  16. Step 12: Packaging Debug and Release - CMake, https://cmake.org/cmake/help/latest/guide/tutorial/Packaging%20Debug%20and%20Release.html
  17. CMake 사용법 - 코드쉼터, https://serene-code.tistory.com/entry/CMake-%EC%82%AC%EC%9A%A9%EB%B2%95
  18. CMake vs. Make: What’s the Difference? - Earthly Blog, https://earthly.dev/blog/cmake-vs-make-diff/
  19. Hello-world example using CMake - CodeRefinery, https://coderefinery.github.io/cmake/03-hello-world-cmake/
  20. HelloWorld with CMake - UCL, https://github-pages.ucl.ac.uk/research-computing-with-cpp/03cpp2/sec07CMakeHelloWorld.html
  21. Step 1: A Basic Starting Point - CMake, https://cmake.org/cmake/help/latest/guide/tutorial/A%20Basic%20Starting%20Point.html
  22. CMake 익히기 : 기초문법 (1) - 불확정한 세상 - 티스토리, https://karl27.tistory.com/70
  23. [CMake 튜토리얼] 2. CMakeLists.txt 주요 명령과 변수 정리 - ECE …, https://www.tuwlab.com/ece/27260
  24. CMake 할때 쪼오오금 도움이 되는 문서 / GitHub, https://gist.github.com/dongbum/d1d49e38a20f9cf52ea39f9ce2702160
  25. jameskbride/cmake-hello-world: Hello World application using CMake for the build. - GitHub, https://github.com/jameskbride/cmake-hello-world
  26. 모두의 코드 씹어먹는 C++ - <19 - 2. C++ 프로젝트를 위한 CMake 사용법>, https://modoocode.com/332
  27. CMAKE_BUILD_TYPE - CMake 4.1.0-rc4 Documentation, https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html
  28. How to know the build type in CMakeLists.txt if it’s Debug, Release or RelWithDebInfo?, https://learn.microsoft.com/en-us/answers/questions/1251618/how-to-know-the-build-type-in-cmakelists-txt-if-it
  29. jacking75/examples_CMake: CMake 사용법 정리 - GitHub, https://github.com/jacking75/examples_CMake
  30. [CMake] Variable (변수) - 별준 - 티스토리, https://junstar92.tistory.com/206
  31. [CMake] CMakeLists.txt 작성하기 - kkanalog - 티스토리, https://kkastory.tistory.com/29
  32. [CMAKE] Cache 변수 - 알쏭달쏭 - 티스토리, https://answer-me.tistory.com/121
  33. [CMake] Flow Control - if - 별준 - 티스토리, https://junstar92.tistory.com/209
  34. foreach - CMake 4.1.0-rc4 Documentation, https://cmake.org/cmake/help/latest/command/foreach.html
  35. 3.8. Control structures - CGold 0.1 documentation - Read the Docs, https://cgold.readthedocs.io/en/latest/tutorials/control-structures.html
  36. CMake 명령어 - Shumin Blog -, http://shumin.co.kr/cmake-%EB%AA%85%EB%A0%B9%EC%96%B4/
  37. Effective Modern CMake - GitHub Gist, https://gist.github.com/mbinna/c61dbb39bca0e4fb7d1f73b0d66a4fd1?permalink_comment_id=3050247
  38. Effective Modern CMake - GitHub Gist, https://gist.github.com/mbinna/c61dbb39bca0e4fb7d1f73b0d66a4fd1
  39. Modern CMake with target_link_libraries - Schneide Blog, https://schneide.blog/2016/04/08/modern-cmake-with-target_link_libraries/
  40. Target-based build systems with CMake, https://enccs.github.io/cmake-workshop/targets/
  41. It’s Time To Do CMake Right - pablo arias, https://pabloariasal.github.io/2018/02/19/its-time-to-do-cmake-right/
  42. [C++] CMake로 프로젝트 빌드하기 - velog, https://velog.io/@zzwon1212/Cpp-CMake%EB%A1%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%B9%8C%EB%93%9C%ED%95%98%EA%B8%B0
  43. target_link_libraries - CMake 4.1.0-rc4 Documentation, https://cmake.org/cmake/help/latest/command/target_link_libraries.html
  44. Examples of when PUBLIC/PRIVATE/INTERFACE should be used in cmake, https://stackoverflow.com/questions/69783203/examples-of-when-public-private-interface-should-be-used-in-cmake
  45. cmake-buildsystem(7), https://cmake.org/cmake/help/latest/manual/cmake-buildsystem.7.html
  46. add_library - CMake 4.1.0-rc4 Documentation, https://cmake.org/cmake/help/latest/command/add_library.html
  47. Step 10: Selecting Static or Shared Libraries - CMake, https://cmake.org/cmake/help/latest/guide/tutorial/Selecting%20Static%20or%20Shared%20Libraries.html
  48. Is it possible to get CMake to build both a static and shared library at the same time?, https://stackoverflow.com/questions/2152077/is-it-possible-to-get-cmake-to-build-both-a-static-and-shared-library-at-the-sam
  49. BUILD_SHARED_LIBS - CMake 4.1.0-rc4 Documentation, https://cmake.org/cmake/help/latest/variable/BUILD_SHARED_LIBS.html
  50. add_subdirectory - CMake 4.1.0-rc4 Documentation, https://cmake.org/cmake/help/latest/command/add_subdirectory.html
  51. cmake behaviour with add_subdirectory - Code, https://discourse.cmake.org/t/cmake-behaviour-with-add-subdirectory/3680
  52. CMake with subdirectories - c++ - Stack Overflow, https://stackoverflow.com/questions/42744315/cmake-with-subdirectories
  53. FindBoost - CMake 3.8.2 Documentation, https://cmake.org/cmake/help/v3.8/module/FindBoost.html?highlight=i
  54. FindBoost - CMake 4.1.0-rc4 Documentation, https://cmake.org/cmake/help/latest/module/FindBoost.html
  55. Boost library - Modern CMake, https://cliutils.gitlab.io/modern-cmake/chapters/packages/Boost.html
  56. Add Boost C++ Libraries as a dependency with plain CMake - GitHub Gist, https://gist.github.com/FlorianWolters/11225791
  57. How to properly include boost module with cmake find_package? - Stack Overflow, https://stackoverflow.com/questions/75815541/how-to-properly-include-boost-module-with-cmake-find-package